package com.see.servise;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import com.see.domain.OpenBook;
import com.see.domain.Order;
import com.see.exception.NoSuchProductException;
import com.see.exception.ProductExistsException;
import com.see.server.ServerCore;

public class SEEServise {
	
    private HashMap<String, OpenBook> books = new HashMap<>();
    private volatile static SEEServise instance;
    private HashMap<String, ArrayList<Order>> deferredUserMatchOrders = new HashMap<>();
    private ServerCore serverCore;
    
    private Thread ordersThread;
    private LinkedBlockingQueue<Order> orderQueue = new LinkedBlockingQueue<>();
    
	public OrderCounter orderCounter = new OrderCounter();
	
	private static Logger logger = LoggerFactory.getLogger(SEEServise.class);

    private SEEServise() {
    }
    
    public static SEEServise getInstance() {       
        if (instance == null) {
            synchronized(SEEServise.class) {
                if (instance == null)
                	instance = new SEEServise();
                
            }
        }
        return instance;
    }
    
	public void start() {
		ordersThread = new Thread(new Runnable() {
			@Override
			public void run() {
				while (!Thread.interrupted()) {
					Order order = null;
					try {
						order = orderQueue.take();
					} catch (InterruptedException e) {
						//don't matter
					}
					
					submitOrder(order);
				}
			}
		});
		ordersThread.start();
	}
    
    public synchronized ArrayList<String> getProductList() {
    	return new ArrayList<>(books.keySet());
    }
    
    public synchronized void createProductBook(String product) throws ProductExistsException {
    	OpenBook book = getProductBook(product);
		if (book != null) {
            throw new ProductExistsException("ProductExistsException: Product is already on the books.");
        } else {
            OpenBook newBook = new OpenBook(product, this);
            books.put(product, newBook);
        }
    }
    
    /**
     * This method will add order to Open book.
     *
     * @param  order
     * @throws NoSuchProductException
     */
    private synchronized void submitOrder(Order order)  {
		OpenBook book = getProductBook(order.getProduct());
		
		book.tryMatchOrder(order);
		
		if (!order.isMatch()) {
			book.addToBook(order);
		}
    }
    
    private synchronized OpenBook getProductBook(String product){
		OpenBook book = null;
		book = books.get(product);
		return book;
    }
    
    public synchronized boolean checkProductBookExist(String product) {
    	OpenBook book = getProductBook(product);
		if (book == null) {
			return false;
		}
		return true;
    }
    
    public synchronized void addToOrderQueue(Order order) {
        orderQueue.offer(order);
    }
    
    public synchronized void addDeferredMatchOrdersToUser(String userName, Order order) {
    	ArrayList<Order> userOrders = deferredUserMatchOrders.get(userName);
    	if(userOrders == null){
    		userOrders = new ArrayList<Order>();
    		deferredUserMatchOrders.put(userName, userOrders);
    	}
    	logger.info("Filled order add to " + userName + " notificate map, order ID = " + order.getId());
    	userOrders.add(order);  
    	
	}
    
    public synchronized ArrayList<Order> getDeferredMatchOrdersFromUser(String userName){
    	ArrayList<Order> userFilledOrder = deferredUserMatchOrders.remove(userName);
    	if(userFilledOrder != null){
    		logger.info("Filled order remove from " + userName + " notificate map");
    		return userFilledOrder;
    	}
    	return new ArrayList<Order>();
    }
    

	public synchronized ServerCore getServerCore() {
		return serverCore;
	}

	public synchronized void setServerCore(ServerCore serverCore) {
		this.serverCore = serverCore;
	}
	
	public synchronized void sendMatchOrderToClient(Order order){
		if(serverCore != null)
			serverCore.sendMatchOrderToClient(order);
	}
    
	public class OrderCounter {
		private AtomicInteger count = new AtomicInteger(0);
		
		public synchronized void incrementCount() {
			count.incrementAndGet();
		}
		
		public synchronized int incrementAndGet(){
			return count.incrementAndGet();
		}
		
		public synchronized int getCount() {
			return count.get();
		}
	}
}

